package com.chengke.novel.common.annotation;


import com.chengke.novel.common.exception.BusinessException;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁 切面
 * @Author CK
 * @Date 2023/3/20
 */

@Aspect
@Component
@RequiredArgsConstructor
public class LockAspect {


    private final RedissonClient redissonClient;

    // Redis 锁的前缀
    private static final String KEY_PREFIX = "Lock";

    // Redis 锁 key 的分隔符
    private static final String KEY_SEPARATOR = "::";

    @Around(value = "@annotation(com.chengke.novel.common.annotation.Lock)")
    @SneakyThrows
    public Object doAround(ProceedingJoinPoint joinPoint) {
        // 用于获取被拦截的方法的签名信息，包括方法名、参数类型和返回类型等信息，以及获取该方法的 Method 对象
        // 1.获取到该连接点对应的方法的签名信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        // 2.获取该方法的 Method 对象，以便后续使用该对象调用目标方法
        Method targetMethod = methodSignature.getMethod();
        // 获取方法上的 @Lock 注解
        Lock lock = targetMethod.getAnnotation(Lock.class);
        // 构造 Redis 锁的 key
        String lockKey = KEY_PREFIX + buildLockKey(lock.prefix(), targetMethod,
                joinPoint.getArgs());
        // 使用 Redisson 客户端获取 Redis 锁
        RLock rLock = redissonClient.getLock(lockKey);
        if (lock.isWait() ? rLock.tryLock(lock.waitTime(), TimeUnit.SECONDS) : rLock.tryLock()) {
            // 如果加锁成功，则执行目标方法
            try {
                return joinPoint.proceed();
            } finally {
                // 释放锁
                rLock.unlock();
            }
        }
        // 如果加锁失败，则抛出业务异常
        throw new BusinessException(lock.failCode());
    }

    private String buildLockKey(String prefix, Method method, Object[] args) {
        StringBuilder builder = new StringBuilder();
        if (StringUtils.hasText(prefix)) {
            builder.append(KEY_SEPARATOR).append(prefix);
        }
        // 获取被拦截方法的所有参数信息 然后
        Parameter[] parameters = method.getParameters();
        // 遍历每个参数，判断该参数是否被 @Key 注解标记，如果是，则解析注解中的表达式，并将计算结果作为 Redis 锁的一部分
        for (int i = 0; i < parameters.length; i++) {
            builder.append(KEY_SEPARATOR);
            if (parameters[i].isAnnotationPresent(Key.class)) {
                Key key = parameters[i].getAnnotation(Key.class);
                // 构造 Redis 锁的 key
                builder.append(parseKeyExpr(key.expr(), args[i]));
            }
        }
        return builder.toString();
    }

    /**
     * 解析 @Key 注解中的表达式，计算表达式的值，并将结果作为 Redis 锁的一部分
     * @param expr key前缀
     * @param arg 参数
     * @return String
     */
    private String parseKeyExpr(String expr, Object arg) {
        if (!StringUtils.hasText(expr)) {
            return arg.toString();
        }
        // 获取解析表达式对象
        ExpressionParser parser = new SpelExpressionParser();
        // 解析表达式,得到结果
        Expression expression = parser.parseExpression(expr, new TemplateParserContext());
        // 计算表达式的值，并返回
        return expression.getValue(arg, String.class);
    }

}
